package procilege.impl;

import static merisis.util.BitUtil.toStringOfBitSetCollection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

import merisis.MerisisVM;
import merisis.traitspace.Morphism;
import merisis.util.BitUtil;

import org.apache.solr.util.OpenBitSet;

import procilege.compute.ExprEvaluation;
import procilege.compute.ExprExpand;
import procilege.compute.ExprExpandTraveller;
import procilege.compute.ExprFormatter;
import procilege.lang.BoolExprAnd;
import procilege.lang.BoolExprElement;
import procilege.lang.EntityExprAnd;
import procilege.lang.EntityExprElement;
import procilege.lang.ExprElement;
import procilege.lang.Name;
import procilege.lang.PredicateExpr;
import procilege.lang.PrivExprComposition;
import procilege.lang.PrivExprElement;
import procilege.lang.PrivExprMergence;
import procilege.lang.PrivilegeForm;
import procilege.lang.PrivilegeFormAnalysis;
import procilege.lang.PrivilegeFormTerm;

import com.google.inject.Inject;

public class PrivFormAnalysisImpl implements PrivilegeFormAnalysis {
	@Inject
	private ExprExpand expand;

	@Inject
	private MerisisVM ts;

	@Inject
	private ExprEvaluation comp;

	@Inject
	private ExprFormatter ftr;

	@Inject
	private ComputationalEntityMgr cem;

	@Inject
	private Logger logger;

	public Collection<PrivilegeFormTerm> expandableTerms(PrivilegeForm privform) {

		PrivilegeFormImpl pf = (PrivilegeFormImpl) privform;

		if (pf.isDirty)
			expand(pf);

		Collection<PrivilegeFormTerm> result = new ArrayList<PrivilegeFormTerm>();

		// combine the result
		synchronized (pf.functionMap) {
			for (Collection<PrivilegeFormTerm> c : pf.functionMap.values()) {
				result.addAll(c);
			}
		}

		return result;
	}

	public Collection<PrivilegeFormTerm> normalTerms(PrivilegeForm privform) {
		PrivilegeFormImpl pf = (PrivilegeFormImpl) privform;

		if (pf.isDirty)
			expand(pf);

		Collection<PrivilegeFormTerm> result = new ArrayList<PrivilegeFormTerm>();

		// combine the result
		synchronized (pf.functionMap) {
			for (long i = pf.reducedFunctions.nextSetBit(0); i >= 0; i = pf.reducedFunctions
					.nextSetBit(i + 1)) {
				OpenBitSet bs = new OpenBitSet();
				bs.set(i);
				result.addAll(pf.functionMap.get(bs));
			}
		}

		return result;
	}

	public boolean available(PrivilegeFormTerm t) {

		for (PredicateExpr pde : t.getRestrictions()) {

			Object val = comp.eval(pde);
			if (val instanceof Boolean) {
				if (!((Boolean) val))
					return false;
			} else
				return false;
		}

		return true;
	}

	public boolean available(PrivilegeForm privform) {
		PrivilegeFormImpl pf = (PrivilegeFormImpl) privform;

		if (pf.isDirty)
			expand(pf);

		for (long i = pf.reducedFunctions.nextSetBit(0); i >= 0; i = pf.reducedFunctions
				.nextSetBit(i + 1)) {

			Collection<PrivilegeFormTerm> terms;

			OpenBitSet bs = new OpenBitSet();
			bs.set(i);
			terms = pf.functionMap.get(bs);
			for (PrivilegeFormTerm p : terms) {
				if (available(p))
					continue;
				return false;
			}
		}

		return true;
	}

	@SuppressWarnings("unchecked")
	public boolean congruent(PrivilegeForm pfm1, PrivilegeForm pfm2) {

		PrivilegeFormImpl pf1 = (PrivilegeFormImpl) pfm1;
		PrivilegeFormImpl pf2 = (PrivilegeFormImpl) pfm2;

		if (pf1.isDirty)
			expand(pf1);

		if (pf2.isDirty)
			expand(pf2);

		// By the natural order of bitset, the two forms are parellel rigorously
		// Although the parellel forms,
		// check the cardinalities first in an economic intention
		if (pf1.reducedFunctions.cardinality() != pf2.reducedFunctions
				.cardinality())
			return false;

		long i = pf1.reducedFunctions.nextSetBit(0);
		long j = pf2.reducedFunctions.nextSetBit(0);
		while (i >= 0 && j >= 0) {

			if (i != j)
				return false; // they aren't parellel!

			// 2x2 square, [{0==I,1==J}][{0==FALSE, 1==TRUE}]
			// Collection<OpenBitSet>[][] square = new Collection[][] {
			// { null, null }, { null, null } };

			HashSet<OpenBitSet> t1EntGroup = new HashSet<OpenBitSet>();
			HashSet<OpenBitSet> t2EntGroup = new HashSet<OpenBitSet>();
			HashSet<OpenBitSet> f1EntGroup = new HashSet<OpenBitSet>();
			HashSet<OpenBitSet> f2EntGroup = new HashSet<OpenBitSet>();

			OpenBitSet bs;
			bs = new OpenBitSet();
			bs.set(i);
			for (PrivilegeFormTerm p : pf1.functionMap.get(bs)) {
				if (available(p)) {
					t1EntGroup.add(p.getEntities());
					// addSquare(square[0], 1, p.getEntities());
				} else {
					f1EntGroup.add(p.getEntities());
					// addSquare(square[0], 0, p.getEntities());
				}
			}

			for (PrivilegeFormTerm p : pf2.functionMap.get(bs)) {
				if (available(p)) {
					t2EntGroup.add(p.getEntities());
					// addSquare(square[1], 1, p.getEntities());
				} else {
					f2EntGroup.add(p.getEntities());
					// addSquare(square[1], 0, p.getEntities());
				}
			}

			if (logger.isLoggable(Level.FINEST)) {

				logger.finest(String.format(
						"proc of func %d, avail: %s == %s; inavail: %s == %s",
						i, toStringOfBitSetCollection(t1EntGroup),
						toStringOfBitSetCollection(t2EntGroup),
						toStringOfBitSetCollection(f1EntGroup),
						toStringOfBitSetCollection(f2EntGroup)));
			}

			// assert the entity equivalence being available
			if (!isEntitySetEquivalent(t1EntGroup, t2EntGroup)) {
				if (logger.isLoggable(Level.FINEST)) {
					logger.finest("the available proceses is not proofed!");
				}
				return false;
			}
			
			// assert the entity equivalence being inavailable
			if (!f1EntGroup.isEmpty() && !f2EntGroup.isEmpty()
					&& !isEntitySetEquivalent(f1EntGroup, f2EntGroup)) {
				if (logger.isLoggable(Level.FINEST)) {
					logger.finest("the inavailable proceses is not proofed!");
				}
				return false;
			}

			i = pf1.reducedFunctions.nextSetBit(i + 1);
			j = pf2.reducedFunctions.nextSetBit(j + 1);
		}

		return true;
	}

	private boolean isEntitySetEquivalent(Collection<OpenBitSet> m,
			Collection<OpenBitSet> n) {

		long[][] a = new long[m.size()][];
		int i = 0;
		for (OpenBitSet s : m) {
			a[i++] = BitUtil.toArray(s);
		}

		long[][] b = new long[n.size()][];
		i = 0;
		for (OpenBitSet s : n) {
			b[i++] = BitUtil.toArray(s);
		}

		return cem.equiv(a, b);
	}

	private void expand(PrivilegeFormImpl pf) {

		PrivilegeFormTerm[] terms = null;

		terms = toTerms(normlize(pf));

		if (terms == null)
			return;

		synchronized (pf.functionMap) {

			pf.functionMap.clear();

			// separating these terms by employment
			for (PrivilegeFormTerm t : terms) {
				OpenBitSet funcs = t.getFucntions();
				Collection<PrivilegeFormTerm> ts = pf.functionMap.get(funcs);
				if (ts == null) {
					ts = new HashSet<PrivilegeFormTerm>();
					pf.functionMap.put(funcs, ts);
				}

				ts.add(t);
			}
		}

		OpenBitSet funcs = new OpenBitSet();
		for (OpenBitSet t : pf.functionMap.keySet()) {

			// select the only one, the first and the last.
			long i;
			i = t.nextSetBit(0);
			if (i < 0)
				continue;

			long j = t.nextSetBit(i + 1);

			if (j >= 0)
				continue;

			funcs.set(i);
		}

		pf.reducedFunctions = funcs;

		pf.isDirty = false;
	}

	private PrivilegeFormTerm[] toTerms(Collection<PrivExprMergence> pvs) {

		Collection<PrivilegeFormTerm> result = new HashSet<PrivilegeFormTerm>();

		for (ExprElement pe : pvs) {

			PrivilegeFormTerm pft = new PrivilegeFormTerm();

			if (pe instanceof PrivExprMergence) {

				PrivExprMergence pm = (PrivExprMergence) pe;
				ExprElement be = pm.getRestrictionExpr();
				if (be instanceof BoolExprAnd) {

					for (BoolExprElement t : ((BoolExprAnd) be).getTerms()) {

						pft.mergePredicateExpr((PredicateExpr) t);
					}
				} else if (be != null) {

					pft.mergePredicateExpr((PredicateExpr) be);
				}

				be = pm.getEntityExpr();
				if (be instanceof EntityExprAnd) {

					for (EntityExprElement t : ((EntityExprAnd) be).getTerms()) {

						pft.mergeEntityName(t);
					}
				} else if (be != null) {

					pft.mergeEntityName((EntityExprElement) be);
				}

				for (PrivExprElement p : pm.getPrivileges()) {
					pft.mergePrivExpr((PrivExprElement) p);
				}

				//TODO : bound names
				//Iterator<Name> oitr = pm.originsIterator();
//				while (oitr.hasNext()) {
//					pft.addOrigin(oitr.next());
//				}
			} else if (pe instanceof Name) {
				pft.mergePrivExpr((Name) pe);

			} else
				throw new UnsupportedOperationException("class: " + pe);

			result.add(pft);
		}

		return result.toArray(new PrivilegeFormTerm[result.size()]);
	}

	public PrivilegeForm privform(PrivExprElement expr) {

		PrivilegeFormImpl pf;
		pf = new PrivilegeFormImpl(expr);
		pf.isDirty = true;
		return pf;
	}

	public PrivilegeForm privform(Name name) {

		return cem.retrievePrivlegeForm(name);
	}

	public Collection<PrivExprMergence> normlize(Name name) {

		return _normlize(expand.traveller(name), ((NameImpl) name).nameId);
	}

	public Collection<PrivExprMergence> normlize(PrivilegeForm form) {

		return _normlize(expand.traveller(null),
				((PrivilegeFormImpl) form).expr);
	}

	private Collection<PrivExprMergence> _normlize(ExprExpandTraveller tr,
			long name) {

		PrivilegeFormImpl pf = (PrivilegeFormImpl) privform(new NameImpl(name));
		if (pf == null)
			return null;
		
		return _normlize(tr.spawn(name), pf.expr);
	}

	public Collection<PrivExprMergence> _normlize(ExprExpandTraveller tr,
			PrivExprElement expr) {

		if (expr == null)
			return null;

		ArrayList<PrivExprMergence> list = new ArrayList<PrivExprMergence>();

		PrivExprComposition expanded;
		expanded = (PrivExprComposition) tr.expandPrivExpr(expr);
		for (PrivExprElement exp : expanded.getTerms()) {

			PrivExprMergence m = (PrivExprMergence) exp;

			ExprExpandTraveller subtr;
			subtr = tr.spawn(m.getBoundNames());
			OpenBitSet fn = m.getFreeNames();
			if (fn.isEmpty()) {
				list.add(m);
				continue;
			}

			for (long n = fn.nextSetBit(0); n >= 0; n = fn.nextSetBit(n + 1)) {
				
				Collection<PrivExprMergence> nexp = _normlize(subtr, n);
				if (nexp == null)
					subtr.boundname(n);
				else
					subtr.apply(n, nexp);
			}
			list.addAll(_normlize(subtr, m));
		}

		if (logger.isLoggable(Level.FINEST)) {
			StringBuilder sb = new StringBuilder();
			sb.append("normalized, bound names: {");
			BitUtil.format(sb, tr.getBoundNames(), ",");
			sb.append("}\n");
			int i = 0;
			for (PrivExprMergence term : list) {
				sb.append(String.format("%04d: '", i++));
				ftr.format(sb, term);
				sb.append("', fn:{");
				BitUtil.format(sb, term.getFreeNames(), ",");
				sb.append("}\n");
			}
			logger.finest(sb.toString());
		}

		return list;
	}
}
